package ch.usi.inf.pf2.analyzer;

import ch.usi.inf.pf2.alu.IdentifierRecognizer;
import ch.usi.inf.pf2.alu.IdentifierType;
import ch.usi.inf.pf2.alu.UnrecognizedIdentifierException;

/**
 * @author Thomas Mantegazzi,Yotam Sharon
 * 
 * @date 16 april 2010
 */

/**
 * Analyzer is the class where the input string is analyzed and divided in
 * token.
 */

public class Analyzer {

	private final TokenRecognizer[] recognizers;
	private final IdentifierRecognizer[] identifiers;
	private int currentPosition;
	private Token currentToken;
	private String text;

	public Analyzer() {
		recognizers = new TokenRecognizer[] {
				new TokenRecognizer(TokenType.PLUS, "\\+"),
				new TokenRecognizer(TokenType.MINUS, "\\-"),
				new TokenRecognizer(TokenType.SLASH, "\\/"),
				new TokenRecognizer(TokenType.STAR, "\\*"),
				new TokenRecognizer(TokenType.CELL_REFERENCE,
						"[A-Z]+[1-9][0-9]*"),
				new TokenRecognizer(TokenType.IDENTIFIER,
						"[A-Za-z][A-Za-z0-9_]*"),
				new TokenRecognizer(TokenType.FLOAT_LITERAL,
						"[0-9]+\\.[0-9]+|[0-9]+|\\.[0-9]+"),
				new TokenRecognizer(TokenType.COLON, "\\:"),
				new TokenRecognizer(TokenType.CLOSED_PARENTHESIS, "\\)"),
				new TokenRecognizer(TokenType.OPEN_PARENTHESIS, "\\("),
				new TokenRecognizer(TokenType.POWER, "\\^"),
				new TokenRecognizer(TokenType.MODULO, "\\%"),
				new TokenRecognizer(TokenType.COMMA, "\\,") };
		identifiers = new IdentifierRecognizer[] {
				new IdentifierRecognizer(IdentifierType.AVERAGE, "average"),
				new IdentifierRecognizer(IdentifierType.COS, "cos"),
				new IdentifierRecognizer(IdentifierType.COT, "cot"),
				new IdentifierRecognizer(IdentifierType.LN, "ln"),
				new IdentifierRecognizer(IdentifierType.LOG, "log"),
				new IdentifierRecognizer(IdentifierType.MAX, "max"),
				new IdentifierRecognizer(IdentifierType.MIN, "min"),
				new IdentifierRecognizer(IdentifierType.SIN, "sin"),
				new IdentifierRecognizer(IdentifierType.SUM, "sum"),
				new IdentifierRecognizer(IdentifierType.TAN, "tan") };
		currentPosition = 0;
		this.text = null;
	}

	public void setText(String text) {
		currentPosition = 0;
		currentToken = null;
		this.text = text;
		for (TokenRecognizer r : recognizers) {
			r.setText(text);
		}
	}

	public void fetchNextToken() throws UnrecognizedTokenException,
			UnrecognizedIdentifierException {
		int maxTokenLength = 0;
		TokenRecognizer longestMatch = null;
		if (currentPosition + 1 > text.length()) {
			currentToken = new Token(null, text.length() - 1,
					TokenType.END_OF_TEXT);
		} else {
			for (TokenRecognizer r : recognizers) {
				if (r.find(currentPosition)) {
					if (r.getTokenLength() > maxTokenLength) {
						maxTokenLength = r.getTokenLength();
						longestMatch = r;
					}
				}
			}
			if (text.charAt(currentPosition) == ' '
					|| (currentPosition == 0 && text.charAt(currentPosition) == '=')) {
				currentPosition++;
				fetchNextToken();
			} else if (longestMatch == null) {
				throw new UnrecognizedTokenException(
						"Error: Symbol not recognized at position "
								+ currentPosition + ", '"
								+ text.charAt(currentPosition) + "..'");
			} else {
				currentPosition = longestMatch.getTokenLength()
						+ longestMatch.getToken().getStartPosition();
				currentToken = longestMatch.getToken();
				// check the validity of the identifier
				if (currentToken.getType() == TokenType.IDENTIFIER) {
					if (!isValidIdentifier(currentToken)) {
						throw new UnrecognizedIdentifierException(
								"Error: unrecognized identifier at position "
										+ currentToken.getStartPosition()
										+ ": '" + currentToken.getText() + "'");
					}
				}
			}
		}
	}

	public Token getCurrentToken() {
		return currentToken;
	}

	/**
	 * Check the validity of an identifier stored in the given Token
	 * 
	 * @param token
	 *            The token which represent the identifier
	 * @return True if the Token represent a valid identifier
	 */
	private boolean isValidIdentifier(Token token) {
		for (IdentifierRecognizer identifier : identifiers) {
			if (identifier.getText().equals(
					token.getText().trim().toLowerCase())) {
				return true;
			}
		}
		return false;
	}

}
